package logs

import (
	"fmt"
	"os"
	"path"
	"qingbing/xztools/chans"
	"qingbing/xztools/crons"
	"qingbing/xzutils/files"
	"qingbing/xzutils/formats"
	"qingbing/xzutils/strs"
)

// 通道日志文件结构
type FileLog struct {
	// 日志文件属性
	ID           string // 日志标识
	Dir          string
	Filename     string // 未设置文件名时自动用日期作为文件名
	TimeInterval crons.TimeInterval
	// 文件大小控制属性
	MaxSize int64
	// 通道数据格式函数
	Formatter func(data *chans.ChanData) string
	// 继承通道的功能
	ch *chans.Chan
	// 继承日志记录组件
	fr *FileRecord
	// 私有属性
	currentSize int64
	logFile     string
	fp          *os.File
	task        *crons.Cron
}

/**
 * 向通道推送数据
 */
func (fl FileLog) Append(data any) {
	fl.ch.Append(data)
}

/**
 * 通道数据处理函数
 */
func (fl *FileLog) consume(data *chans.ChanData) {
	/**
	 * 判断文件是否超出大小限制
	 */
	if fl.MaxSize > 0 && fl.currentSize > fl.MaxSize {
		if fl.fp != nil {
			fl.fp.Close()
			fl.fp = nil
		}
		// 文件超出大小进行切片
		cutFile, err := files.CutFile(fl.logFile)
		if err != nil {
			fl.fr.PutLog(fmt.Sprintf("日志剪切。 File :%s; Size: %d", cutFile, fl.currentSize))
		}
	}

	/**
	 * 打开日志文件句柄
	 */
	if fl.fp == nil {
		stat, err := os.Stat(fl.logFile)
		if err != nil {
			fl.currentSize = 0
		} else {
			fl.currentSize = stat.Size()
		}
		if fp, err := files.OpenFile(fl.logFile); err != nil {
			return
		} else {
			fl.fp = fp
			fl.fr.PutLog(fmt.Sprintf("打开日志文件。 File :%s; Size: %d", fl.logFile, fl.currentSize))
		}
	}

	/**
	 * 数据写入主日志
	 */
	var num int
	var err error
	if fl.Formatter != nil {
		num, err = fl.fp.WriteString(fl.Formatter(data) + "\n")
	} else {
		switch data.Data.(type) {
		case string:
			num, err = fl.fp.WriteString(fmt.Sprintf("[%s]: %s\n", data.Time.Format("2006-01-02 15:04:05.000000"), data.Data.(string)))
		default:
			num, err = fl.fp.WriteString(fmt.Sprintf("[%s]: %+v\n", data.Time.Format("2006-01-02 15:04:05.000000"), data.Data))
		}
	}
	if err != nil {
		fl.fr.PutLog(fmt.Sprintf("数据写入日志失败: %v", data))
		return
	}
	if fl.MaxSize > 0 {
		fl.currentSize += int64(num)
	}
}

/**
 * 维护文件名
 */
func (fl *FileLog) defendFilename() {
	// 重新指定文件名
	fl.Filename = formats.Date(fl.TimeInterval.DataFormat())
	fl.logFile = path.Join(fl.Dir, fmt.Sprintf("%s_%s.log", fl.ID, fl.Filename))
	// 重置句柄和当前操作文件属性
	if fl.fp != nil {
		fl.fp.Close()
	} else {
		fl.fp = nil
	}
	stat, err := os.Stat(fl.logFile)
	if err != nil {
		fl.currentSize = 0
	} else {
		fl.currentSize = stat.Size()
	}
}

/**
 * 启动日志工具
 */
func (fl *FileLog) Run() error {
	/**
	 * 启用组件日志通道
	 */
	fl.fr = &FileRecord{
		ID:          fl.ID,
		Dir:         fl.Dir,
		MinLogLevel: LOG_LEVEL_DEBUG,
		Filename:    "file_log.log",
		MaxSize:     1024 * 1024 * 2,
	}
	err := fl.fr.Run() // 启动
	if err != nil {
		return fmt.Errorf("启动组件的日志文件失败: %v\n", err)
	}

	/**
	 * 日志文件名维护
	 */
	if fl.Filename != "" {
		fl.logFile = path.Join(fl.Dir, fmt.Sprintf("%s_%s", fl.ID, fl.Filename))
	} else {
		fl.task = crons.New()
		fl.task.AddFunc(fl.TimeInterval.String(), fl.defendFilename, true)
		fl.task.Start()
	}

	/**
	 * 启用通道文件日志通道
	 */
	fl.ch = &chans.Chan{
		ID:          strs.Join("_", fl.ID, "filelog"),
		ChanSize:    100,
		FuncConsume: fl.consume,
	}
	err = fl.ch.Run()
	if err != nil {
		fl.fr.PutError(fmt.Sprintf("启动日志失败, ID: %s", fl.ID))
		return err
	} else {
		fl.fr.PutLog(fmt.Sprintf("启动日志成功, ID: %s", fl.ID))
		return nil
	}
}

/**
 * 停用组件
 */
func (fl *FileLog) Close() {
	if fl.task != nil {
		fl.task.Stop()
	}
	if fl.fr != nil {
		fl.fr.Close()
	}
	if fl.fp != nil {
		fl.fp.Close()
	}
	if fl.ch != nil {
		fl.ch.Close()
	}
}

/**
 * 文件日志配置数据结构
 */
type ConfFileLog struct {
	// 日志文件属性
	ID           string // 日志标识
	Dir          string
	Filename     string // 未设置文件名时自动用日期作为文件名
	TimeInterval crons.TimeInterval
	// 文件大小控制属性
	MaxSize int64
	// 通道数据格式函数
	Formatter func(data *chans.ChanData) string
}

/**
 * 新建 FileLog
 */
func NewFileLog(conf *ConfFileLog) *FileLog {
	fl := &FileLog{
		ID:           conf.ID,
		Dir:          conf.Dir,
		Filename:     conf.Filename,
		TimeInterval: conf.TimeInterval,
		MaxSize:      conf.MaxSize,
		Formatter:    conf.Formatter,
	}
	fl.Run()
	return fl
}
